#include "OpenSSL.h"

#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>

#include <openssl/evp.h>
#include <openssl/rand.h>

namespace openssl {

    std::string toString(Algorithm algorithm) {
        switch (algorithm) {
            case Algorithm::MD5: return "MD5";
            case Algorithm::SHA1: return "SHA1";
            case Algorithm::SHA224: return "SHA224";
            case Algorithm::SHA256: return "SHA256";
            case Algorithm::SHA384: return "SHA384";
            case Algorithm::SHA512: return "SHA512";
            default: return "";
        }
    }

    const int   BuffSize = 16 * 1024;
    std::string generateSalt() {
        const int     saltSize = 16;  // 16字节的盐值
        unsigned char salt[saltSize];
        RAND_bytes(salt, saltSize);
        return std::string(reinterpret_cast<char*>(salt), saltSize);
    }

    std::string Hash(std::string input, Algorithm algorithm) {
        EVP_MD_CTX* context = EVP_MD_CTX_new();
        EVP_DigestInit(context, EVP_get_digestbyname(toString(algorithm).c_str()));

        // 更新上下文，将数据添加到哈希计算中
        EVP_DigestUpdate(context, input.data(), input.size());

        // 计算哈希值
        unsigned char hash[EVP_MAX_MD_SIZE];
        unsigned int  hashLength;

        EVP_DigestFinal_ex(context, hash, &hashLength);
        EVP_MD_CTX_free(context);

        // 转换哈希值为 std::string
        std::stringstream ss;
        for (unsigned int i = 0; i < hashLength; ++i)
            ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);

        return ss.str();
    }

    std::string Hash256(std::string input) {
        return Hash(input, Algorithm::SHA256);
    }

    std::string HashSalted(const std::string& password, const std::string& salt) {
        return Hash(password + salt, Algorithm::SHA256);
    }

    std::string MD5(std::string input) {
        // 计算 MD5 哈希值
        return Hash(input , Algorithm::MD5);
    }

    std::string Hash1(std::string input) {
        return Hash(input , Algorithm::SHA1);
    }

    std::string GetFileHash(std::string fileName, Algorithm algorithm) {
        char buffer[BuffSize] = {0};

        using namespace std;

        ifstream fin;
        fin.open(fileName, ios::in | ios::binary | ios::ate);

        if (!fin.is_open()) {
            throw "无法打开文件";
            return "";
        }

        streamsize fileSize = fin.tellg();
        fin.seekg(0, ios::beg);

        const EVP_MD* md = EVP_get_digestbyname(toString(algorithm).c_str());

        EVP_MD_CTX* context = EVP_MD_CTX_new();
        EVP_DigestInit(context, md);

        size_t count = 0;
        while (fin) {
            size_t read_size = min((size_t)(fileSize - count), (size_t)BuffSize);
            fin.read(buffer, read_size);
            // 更新上下文，将数据添加到哈希计算中
            EVP_DigestUpdate(context, buffer, read_size);
            count += read_size;
            if (count >= fileSize)
                break;
        }
        fin.close();

        // 计算哈希值
        unsigned char hash[EVP_MAX_MD_SIZE];
        unsigned int  hashLength;

        EVP_DigestFinal_ex(context, hash, &hashLength);
        EVP_MD_CTX_free(context);

        // 转换哈希值为 std::string
        std::stringstream ss;
        for (unsigned int i = 0; i < hashLength; ++i) {
            ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
        }
        return ss.str();
    }

    std::string Hash(std::ifstream fin, Algorithm algorithm) {
        using namespace std;
        
        char buffer[BuffSize] = {0};

        fin.seekg(0, ios::end);
        streamsize fileSize = fin.tellg();
        fin.seekg(0, ios::beg);

        const EVP_MD* md = EVP_get_digestbyname(toString(algorithm).c_str());

        EVP_MD_CTX* context = EVP_MD_CTX_new();
        EVP_DigestInit(context, md);

        size_t count = 0;
        while (fin) {
            size_t read_size = min((size_t)(fileSize - count), (size_t)BuffSize);
            fin.read(buffer, read_size);
            // 更新上下文，将数据添加到哈希计算中
            EVP_DigestUpdate(context, buffer, read_size);
            count += read_size;
            if (count >= fileSize)
                break;
        }

        // 计算哈希值
        unsigned char hash[EVP_MAX_MD_SIZE];
        unsigned int  hashLength;

        EVP_DigestFinal_ex(context, hash, &hashLength);
        EVP_MD_CTX_free(context);

        // 转换哈希值为 std::string
        std::stringstream ss;
        for (unsigned int i = 0; i < hashLength; ++i) {
            ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(hash[i]);
        }
        return ss.str();
    }

}  // namespace openssl
